package xjcrypto

import (
	"bytes"
	"encoding/base64"
	"errors"

	//"crypto/aes"
	"crypto/cipher"
	"crypto/des"
	"fmt"
)

//cipher包实现了多个标准的用于包装底层块加密算法的加密算法实现。

/*
DES的操作
加解密实现思路
    加密-CBC分组模式
        1.创建并返回一个使用DES算法的cipher.Block接口
            .秘钥长度为64bit,即64/8 = 8字节(byte)
        2.对最后一个明文分组进行数据填充
            .DES是以64比特的明文(比特序列)为一个单位来进行加密的
            .最后一组不够64bit,则需要进行数据填充
        3.创建一个密码分组为链接模式的,底层使用DES加密的BlockMode接口
        4.加密连续的数据块

    解密
        1.创建并返回一个使用DES算法的cipher.Block接口
        2.创建一个密码分组为链接模式的,底层使用DES解密的BlockMode接口
        3.数据块解密
        4.去掉最后一组的填充数据
*/

//使用des进行对称加密

func DesEncrypt(str, k string) (enstr string, err error) {
	if len(k) != 8 {
		return "", errors.New("k must be a word of length eight.")
	}
	src := []byte(str)
	key := []byte(k)
	b := encryptDES(src, key)
	enstr = base64.StdEncoding.EncodeToString(b)
	return
}

func DesDecrypt(str, k string) (destr string, err error) {
	if len(k) != 8 {
		return "", errors.New("k must be a word of length eight.")
	}
	src, e := base64.StdEncoding.DecodeString(str)
	if e != nil {
		return "", e
	}
	key := []byte(k)
	b := decryptDES(src, key)
	destr = string(b)
	return
}

func paddingText(src []byte, blockSize int) []byte {
	//1.截取加密代码 段数
	// fmt.Println("加密之前的数据", src)
	padding := blockSize - len(src)%blockSize
	//2.无余数
	if padding == 0 {
		padText := bytes.Repeat([]byte{byte(blockSize)}, blockSize)
		src = append(src, padText...)
	} else { //else 一定直接要添加到该右括号的后面,不能令起一行
		//3.有余数
		padText := bytes.Repeat([]byte{byte(padding)}, padding)
		src = append(src, padText...)
	}
	//4.添加余数
	//src = append(src, padText...)
	return src

}

func unPaddingText(src []byte) []byte {
	//1.取出最后一个元素
	lasteum := int(src[len(src)-1])
	//2.删除和最后一个元素相等长的字节
	newText := src[:len(src)-lasteum]
	return newText
}

func encryptDES(src, key []byte) []byte {
	//1.创建并返回一个使用DES算法的cipher.Block接口
	/*
	     func NewCipher(key []byte) (cipher.Block, error)
	   创建并返回一个使用DES算法的cipher.Block接口。
	*/
	block, err := des.NewCipher(key)
	if err != nil {
		fmt.Println(err)
	}
	//2.对最后一个明文分组进行数据填充
	src = paddingText(src, block.BlockSize())

	//3.创建一个密码分组为链接模式的,底层使用DES加密的BlockMode接口
	//iv := make([]byte,block.BlockSize())
	iv := []byte("xdlerpok")
	/*
	     func NewCBCEncrypter(b Block, iv []byte) BlockMode
	   返回一个密码分组链接模式的、底层用b加密的BlockMode接口，初始向量iv的长度必须等于b的块尺寸。
	*/
	blockMode := cipher.NewCBCEncrypter(block, iv)
	/*
	   type BlockMode interface {
	     // 返回加密字节块的大小
	     BlockSize() int
	     // 加密或解密连续的数据块，src的尺寸必须是块大小的整数倍，src和dst可指向同一内存地址
	     CryptBlocks(dst, src []byte)
	   }
	*/
	//4.加密连续的数据块
	dst := make([]byte, len(src))
	blockMode.CryptBlocks(dst, src)

	return dst
}

func decryptDES(src, key []byte) []byte {
	//1.创建并返回一个使用DES算法的cipher.Block接口
	Block, err := des.NewCipher(key)
	if err != nil {
		panic(err)
	}
	//2.创建一个密码分组为链接模式的,底层使用DES解密的BlockMode接口
	//iv := make([]byte,Block.BlockSize())
	iv := []byte("xdlerpok")
	//func NewCBCDecrypter(b Block, iv []byte) BlockMode
	//返回一个密码分组链接模式的、底层用b解密的BlockMode接口，初始向量iv必须和加密时使用的iv相同。
	blockMode := cipher.NewCBCDecrypter(Block, iv)
	//3.数据块解密
	//dst := make([]byte,len(src))
	//blockMode.CryptBlocks(dst,src)
	blockMode.CryptBlocks(src, src)
	//4.去掉最后一组的填充数据
	newText := unPaddingText(src)
	return newText
}

func main() {
	fmt.Println("====DES加解密====")
	src := []byte("少壮不努力,老大徒伤悲")
	key := []byte("12345678")
	str := encryptDES(src, key)
	str = decryptDES(str, key)

	fmt.Println("解密之后的数据", str)
}
